在多數的情境下,不會獨立存在一支 API,而是由一組 API 來對領域或資源進行存取,以會員服務來說,會有一組包含:註冊、異動、註銷、取得單筆、查詢多筆......等多支 API,才能讓應用端組合不同的 API 滿足需求,在這些 API 中,多數的會員資料結構是一致的。因此,就像在程式碼撰寫時會利用參考來減少重複一樣,撰寫 OpenAPI 規格時我們也可以透過收斂定義並參考引用,降低重複的內容。
首先,我們可以在 components.schemas
節點下,建立一個會員資料結構,命名為 Member
並定義其結構,範例如下:
components:
schemas:
Member:
type: object
properties:
memberId:
type: string
name:
type: string
email:
type: string
format: email
createdAt:
type: string
format: date-time
定義完就可以在取得單筆會員 API 和查詢多筆會員 API 的 Response 中,使用 $ref
參考 Member
結構,範例如下:
# 以上省略
paths:
/members/{memberId}:
get:
summary: 取得單一會員資訊
description: 查詢指定會員的資訊,若該會員不存在時回應 `404 Not Found`。
operationId: getMember
parameters:
- name: memberId
in: path
required: true
description: 會員 ID
schema:
type: string
responses:
'200':
description: 成功取得會員資訊
content:
application/json:
schema:
$ref: '#/components/schemas/Member'
/members:
get:
summary: 取得多筆會員資訊
operationId: getMemberList
parameters:
- name: limit
in: query
description: 限制返回的會員數量
schema:
type: integer
example: 10
- name: offset
in: query
description: 分頁偏移量
schema:
type: integer
example: 0
responses:
'200':
description: 成功取得會員列表
content:
application/json:
schema:
type: object
properties:
members:
type: array
items:
$ref: '#/components/schemas/Member'
components:
schemas:
Member:
type: object
properties:
memberId:
type: string
name:
type: string
email:
type: string
format: email
createdAt:
type: string
format: date-time
不只 schema
,我們也可以在 components
下定義 parameters
和 responses
,善加利用可以很有效減少重複的內容,例如定義 components.schemas.Error
搭配 components.responses.NotFoundResponse
和其他常見的錯誤回應就是不錯的選擇。
最後,為資料結構加上範例資料就可以讓文件更完整,我們可以使用example
節點替單個欄位分別設定,或在資料結構一次完整設定,範例如下:
# 單個欄位設定
components:
schemas:
Member:
type: object
properties:
memberId:
type: string
example: "abc123"
name:
type: string
example: "張三"
email:
type: string
format: email
example: "zhangsan@example.com"
createdAt:
type: string
format: date-time
example: "2024-01-01T00:00:00Z"
# 在 Member 一次設定
components:
schemas:
Member:
type: object
properties:
memberId:
type: string
name:
type: string
email:
type: string
format: email
createdAt:
type: string
format: date-time
example:
memberId: "abc123"
name: "張三"
email: "zhangsan@example.com"
createdAt: "2024-01-01T00:00:00Z"
便可在 Redoc 右側看到我們設定的範例資料如下:
若有多種不同的範例組合,則可以利用 examples
節點,針對不同的場景提供不同的範例,例如 BadRequestError
有缺少必填欄位和資料格式不合法兩種場景,可以設定 examples
如下:
paths:
/members:
get:
summary: 取得多筆會員資訊
operationId: getMemberList
parameters:
# 省略
responses:
'200':
description: 成功取得會員列表
# 省略
'400':
description: 錯誤的請求
content:
application/json:
schema:
$ref: '#/components/schemas/BadRequestError'
examples:
InvalidInput:
$ref: '#/components/examples/InvalidInput'
EmptyInput:
$ref: '#/components/examples/EmptyInput'
components:
schemas:
BadRequestError:
type: object
properties:
errorCode:
type: string
message:
type: string
examples:
InvalidInput:
summary: 不合法的輸入參數
value:
errorCode: "INVALID_INPUT"
message: "不合法的輸入參數"
EmptyInput:
summary: 輸入參數不得為空
value:
errorCode: "EMPTY_INPUT"
message: "輸入參數不得為空"
這時,在 Redoc 右側可看到下拉選單出現不合法的輸入參數
及輸入參數不得為空
兩種範例,如下圖所示:
至此,我們已經可以針對不同情境提供不同的範例,能滿足多數文件的需求。但若要描述的 API 資料結構和情境更加的複雜,出現多型的資料結構時又該如何是好呢?明天,將說明如何在 OpenAPI 中定義多型結構,完成 OpenAPI 基礎規格的最後一哩路。